// Copyright (C) INFINI Labs & INFINI LIMITED.
//
// The INFINI Framework is offered under the GNU Affero General Public License v3.0
// and as commercial software.
//
// For commercial licensing, contact us at:
//   - Website: infinilabs.com
//   - Email: hello@infini.ltd
//
// Open Source licensed under AGPL V3:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package chrono

import (
	"github.com/stretchr/testify/assert"
	"testing"
	"time"
)

const timeLayout = "2006-01-02 15:04:05"

func TestCronExpression_NextTime(t *testing.T) {
	testCases := []struct {
		expression string
		time       string
		nextTimes  []string
	}{
		{
			"* * * * * *",
			"2021-05-31 23:59:56",
			[]string{
				"2021-05-31 23:59:57",
				"2021-05-31 23:59:58",
				"2021-05-31 23:59:59",
				"2021-06-01 00:00:00",
				"2021-06-01 00:00:01",
				"2021-06-01 00:00:02",
			},
		},
		{
			"17/3 * * * * *",
			"2021-03-16 15:04:16",
			[]string{
				"2021-03-16 15:04:17",
				"2021-03-16 15:04:20",
				"2021-03-16 15:04:23",
				"2021-03-16 15:04:26",
				"2021-03-16 15:04:29",
				"2021-03-16 15:04:32",
			},
		},
		{
			"19/3 * * * * *",
			"2021-03-16 15:04:19",
			[]string{
				"2021-03-16 15:04:22",
				"2021-03-16 15:04:25",
				"2021-03-16 15:04:28",
				"2021-03-16 15:04:31",
				"2021-03-16 15:04:34",
				"2021-03-16 15:04:37",
			},
		},
		{
			"8-19/3 * * * * *",
			"2021-03-16 15:04:23",
			[]string{
				"2021-03-16 15:05:08",
				"2021-03-16 15:05:11",
				"2021-03-16 15:05:14",
				"2021-03-16 15:05:17",
				"2021-03-16 15:06:08",
				"2021-03-16 15:06:11",
			},
		},
		{
			"8-24 * * * * *",
			"2021-03-16 15:04:23",
			[]string{
				"2021-03-16 15:04:24",
				"2021-03-16 15:05:08",
				"2021-03-16 15:05:09",
				"2021-03-16 15:05:10",
				"2021-03-16 15:05:11",
				"2021-03-16 15:05:12",
			},
		},
		{
			"0 * * * * *",
			"2021-05-21 13:41:37",
			[]string{
				"2021-05-21 13:42:00",
				"2021-05-21 13:43:00",
				"2021-05-21 13:44:00",
				"2021-05-21 13:45:00",
				"2021-05-21 13:46:00",
				"2021-05-21 13:47:00",
			},
		},
		{
			"7 * * * * *",
			"2021-05-22 13:12:56",
			[]string{
				"2021-05-22 13:13:07",
				"2021-05-22 13:14:07",
				"2021-05-22 13:15:07",
				"2021-05-22 13:16:07",
				"2021-05-22 13:17:07",
				"2021-05-22 13:18:07",
			},
		},
		{
			"0 0 * * * *",
			"2021-05-21 13:41:37",
			[]string{
				"2021-05-21 14:00:00",
				"2021-05-21 15:00:00",
				"2021-05-21 16:00:00",
				"2021-05-21 17:00:00",
				"2021-05-21 18:00:00",
				"2021-05-21 19:00:00",
			},
		},
		{
			"18 15 * * * *",
			"2021-05-21 19:12:56",
			[]string{
				"2021-05-21 19:15:18",
				"2021-05-21 20:15:18",
				"2021-05-21 21:15:18",
				"2021-05-21 22:15:18",
				"2021-05-21 23:15:18",
				"2021-05-22 00:15:18",
			},
		},
		{
			"18 15/5 * * * *",
			"2021-05-21 19:43:56",
			[]string{
				"2021-05-21 19:45:18",
				"2021-05-21 19:50:18",
				"2021-05-21 19:55:18",
				"2021-05-21 20:15:18",
				"2021-05-21 20:20:18",
				"2021-05-21 20:25:18",
			},
		},
		{
			"18 15-30/5 * * * *",
			"2021-05-21 19:43:56",
			[]string{
				"2021-05-21 20:15:18",
				"2021-05-21 20:20:18",
				"2021-05-21 20:25:18",
				"2021-05-21 20:30:18",
				"2021-05-21 21:15:18",
				"2021-05-21 21:20:18",
			},
		},
		{
			"18 40-45 * * * *",
			"2021-05-21 19:43:56",
			[]string{
				"2021-05-21 19:44:18",
				"2021-05-21 19:45:18",
				"2021-05-21 20:40:18",
				"2021-05-21 20:41:18",
				"2021-05-21 20:42:18",
				"2021-05-21 20:43:18",
			},
		},
		{
			"0 0 0 * * *",
			"2020-02-27 13:41:37",
			[]string{
				"2020-02-28 00:00:00",
				"2020-02-29 00:00:00",
				"2020-03-01 00:00:00",
				"2020-03-02 00:00:00",
				"2020-03-03 00:00:00",
				"2020-03-04 00:00:00",
			},
		},
		{
			"45 13 14 * * *",
			"2020-12-28 13:41:37",
			[]string{
				"2020-12-28 14:13:45",
				"2020-12-29 14:13:45",
				"2020-12-30 14:13:45",
				"2020-12-31 14:13:45",
				"2021-01-01 14:13:45",
				"2021-01-02 14:13:45",
			},
		},
		{
			"45 13 14/3 * * *",
			"2020-12-28 13:41:37",
			[]string{
				"2020-12-28 14:13:45",
				"2020-12-28 17:13:45",
				"2020-12-28 20:13:45",
				"2020-12-28 23:13:45",
				"2020-12-29 14:13:45",
				"2020-12-29 17:13:45",
			},
		},
		{
			"45 13 9-16/3 * * *",
			"2020-12-28 13:41:37",
			[]string{
				"2020-12-28 15:13:45",
				"2020-12-29 09:13:45",
				"2020-12-29 12:13:45",
				"2020-12-29 15:13:45",
				"2020-12-30 09:13:45",
				"2020-12-30 12:13:45",
			},
		},
		{
			"45 13 9-16 * * *",
			"2020-12-28 13:41:37",
			[]string{
				"2020-12-28 14:13:45",
				"2020-12-28 15:13:45",
				"2020-12-28 16:13:45",
				"2020-12-29 09:13:45",
				"2020-12-29 10:13:45",
				"2020-12-29 11:13:45",
			},
		},
		{
			"20 45 18 6 * *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-04-06 18:45:20",
				"2020-05-06 18:45:20",
				"2020-06-06 18:45:20",
				"2020-07-06 18:45:20",
				"2020-08-06 18:45:20",
				"2020-09-06 18:45:20",
			},
		},
		{
			"20 45 18 10-12 * *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-04-10 18:45:20",
				"2020-04-11 18:45:20",
				"2020-04-12 18:45:20",
				"2020-05-10 18:45:20",
				"2020-05-11 18:45:20",
				"2020-05-12 18:45:20",
			},
		},
		{
			"20 45 18 5-20/3 * *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-04-05 18:45:20",
				"2020-04-08 18:45:20",
				"2020-04-11 18:45:20",
				"2020-04-14 18:45:20",
				"2020-04-17 18:45:20",
				"2020-04-20 18:45:20",
			},
		},
		{
			"0 0 0 1 * *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-04-01 00:00:00",
				"2020-05-01 00:00:00",
				"2020-06-01 00:00:00",
				"2020-07-01 00:00:00",
				"2020-08-01 00:00:00",
				"2020-09-01 00:00:00",
			},
		},
		{
			"0 0 0 1 1 *",
			"2020-03-27 13:41:37",
			[]string{
				"2021-01-01 00:00:00",
				"2022-01-01 00:00:00",
				"2023-01-01 00:00:00",
				"2024-01-01 00:00:00",
				"2025-01-01 00:00:00",
				"2026-01-01 00:00:00",
			},
		},
		{
			"0 0 0 1 6 *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-06-01 00:00:00",
				"2021-06-01 00:00:00",
				"2022-06-01 00:00:00",
				"2023-06-01 00:00:00",
				"2024-06-01 00:00:00",
				"2025-06-01 00:00:00",
			},
		},
		{
			"0 0 0 1 3-12 *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-04-01 00:00:00",
				"2020-05-01 00:00:00",
				"2020-06-01 00:00:00",
				"2020-07-01 00:00:00",
				"2020-08-01 00:00:00",
				"2020-09-01 00:00:00",
			},
		},
		{
			"0 0 0 1 3-12/3 *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-06-01 00:00:00",
				"2020-09-01 00:00:00",
				"2020-12-01 00:00:00",
				"2021-03-01 00:00:00",
				"2021-06-01 00:00:00",
				"2021-09-01 00:00:00",
			},
		},
		{
			"0 0 0 1 SEP *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-09-01 00:00:00",
				"2021-09-01 00:00:00",
				"2022-09-01 00:00:00",
				"2023-09-01 00:00:00",
				"2024-09-01 00:00:00",
				"2025-09-01 00:00:00",
			},
		},
		{
			"0 0 0 1 AUG-OCT *",
			"2020-03-27 13:41:37",
			[]string{
				"2020-08-01 00:00:00",
				"2020-09-01 00:00:00",
				"2020-10-01 00:00:00",
				"2021-08-01 00:00:00",
				"2021-09-01 00:00:00",
				"2021-10-01 00:00:00",
			},
		},
		{
			"0 0 0 1 5 0",
			"2021-05-23 13:41:37",
			[]string{
				"2022-05-01 00:00:00",
				"2033-05-01 00:00:00",
				"2039-05-01 00:00:00",
				"2044-05-01 00:00:00",
				"2050-05-01 00:00:00",
				"2061-05-01 00:00:00",
			},
		},
		{
			"0 0 0 1 5 0",
			"2021-05-23 13:41:37",
			[]string{
				"2022-05-01 00:00:00",
				"2033-05-01 00:00:00",
				"2039-05-01 00:00:00",
				"2044-05-01 00:00:00",
				"2050-05-01 00:00:00",
				"2061-05-01 00:00:00",
			},
		},
		{
			"0 0 0 1 5 SUN",
			"2021-05-23 13:41:37",
			[]string{
				"2022-05-01 00:00:00",
				"2033-05-01 00:00:00",
				"2039-05-01 00:00:00",
				"2044-05-01 00:00:00",
				"2050-05-01 00:00:00",
				"2061-05-01 00:00:00",
			},
		},
		{
			"0 0 0 1 5 MON",
			"2021-05-23 13:41:37",
			[]string{
				"2023-05-01 00:00:00",
				"2028-05-01 00:00:00",
				"2034-05-01 00:00:00",
				"2045-05-01 00:00:00",
				"2051-05-01 00:00:00",
				"2056-05-01 00:00:00",
			},
		},
		{
			"12 15 13 * * THU-SAT",
			"2021-05-23 13:41:37",
			[]string{
				"2021-05-27 13:15:12",
				"2021-05-28 13:15:12",
				"2021-05-29 13:15:12",
				"2021-06-03 13:15:12",
				"2021-06-04 13:15:12",
				"2021-06-05 13:15:12",
			},
		},
		{
			"12 15 13 * * 4-6",
			"2021-05-23 13:41:37",
			[]string{
				"2021-05-27 13:15:12",
				"2021-05-28 13:15:12",
				"2021-05-29 13:15:12",
				"2021-06-03 13:15:12",
				"2021-06-04 13:15:12",
				"2021-06-05 13:15:12",
			},
		},
		{
			"13-15,46-49 * * * * *",
			"2021-05-21 13:18:14",
			[]string{
				"2021-05-21 13:18:15",
				"2021-05-21 13:18:46",
				"2021-05-21 13:18:47",
				"2021-05-21 13:18:48",
				"2021-05-21 13:18:49",
				"2021-05-21 13:19:13",
			},
		},
		{
			"17-31/5,50-57/4 * * * * *",
			"2021-05-21 13:18:14",
			[]string{
				"2021-05-21 13:18:17",
				"2021-05-21 13:18:22",
				"2021-05-21 13:18:27",
				"2021-05-21 13:18:50",
				"2021-05-21 13:18:54",
				"2021-05-21 13:19:17",
			},
		},
		{
			"17 7-9,54-55 * * * *",
			"2021-05-21 13:02:17",
			[]string{
				"2021-05-21 13:07:17",
				"2021-05-21 13:08:17",
				"2021-05-21 13:09:17",
				"2021-05-21 13:54:17",
				"2021-05-21 13:55:17",
				"2021-05-21 14:07:17",
			},
		},
		{
			"17 8-16/4,50-55/3 * * * *",
			"2021-05-21 13:02:17",
			[]string{
				"2021-05-21 13:08:17",
				"2021-05-21 13:12:17",
				"2021-05-21 13:16:17",
				"2021-05-21 13:50:17",
				"2021-05-21 13:53:17",
				"2021-05-21 14:08:17",
			},
		},
		{
			"17 4 5-9,17-19 * * *",
			"2021-05-21 08:02:17",
			[]string{
				"2021-05-21 08:04:17",
				"2021-05-21 09:04:17",
				"2021-05-21 17:04:17",
				"2021-05-21 18:04:17",
				"2021-05-21 19:04:17",
				"2021-05-22 05:04:17",
			},
		},
		{
			"17 4 5-9/2,16-23/3 * * *",
			"2021-05-21 08:02:17",
			[]string{
				"2021-05-21 09:04:17",
				"2021-05-21 16:04:17",
				"2021-05-21 19:04:17",
				"2021-05-21 22:04:17",
				"2021-05-22 05:04:17",
				"2021-05-22 07:04:17",
			},
		},
		{
			"17 4 17 13-15,26-27 * *",
			"2021-05-14 08:02:17",
			[]string{
				"2021-05-14 17:04:17",
				"2021-05-15 17:04:17",
				"2021-05-26 17:04:17",
				"2021-05-27 17:04:17",
				"2021-06-13 17:04:17",
				"2021-06-14 17:04:17",
			},
		},
		{
			"17 4 17 7-15/4,22-29/3 * *",
			"2021-05-13 08:02:17",
			[]string{
				"2021-05-15 17:04:17",
				"2021-05-22 17:04:17",
				"2021-05-25 17:04:17",
				"2021-05-28 17:04:17",
				"2021-06-07 17:04:17",
				"2021-06-11 17:04:17",
			},
		},
		{
			"17 4 17 16 1-3,11-12 *",
			"2021-02-13 08:02:17",
			[]string{
				"2021-02-16 17:04:17",
				"2021-03-16 17:04:17",
				"2021-11-16 17:04:17",
				"2021-12-16 17:04:17",
				"2022-01-16 17:04:17",
				"2022-02-16 17:04:17",
			},
		},
		{
			"17 4 17 16 JAN-MAR,NOV-DEC *",
			"2021-02-13 08:02:17",
			[]string{
				"2021-02-16 17:04:17",
				"2021-03-16 17:04:17",
				"2021-11-16 17:04:17",
				"2021-12-16 17:04:17",
				"2022-01-16 17:04:17",
				"2022-02-16 17:04:17",
			},
		},
		{
			"17 4 17 16 4-10/3,8-12/2 *",
			"2021-02-13 08:02:17",
			[]string{
				"2021-04-16 17:04:17",
				"2021-07-16 17:04:17",
				"2021-08-16 17:04:17",
				"2021-10-16 17:04:17",
				"2021-12-16 17:04:17",
				"2022-04-16 17:04:17",
			},
		},
		{
			"17 4 17 16 APR-OCT/3,AUG-DEC/2 *",
			"2021-02-13 08:02:17",
			[]string{
				"2021-04-16 17:04:17",
				"2021-07-16 17:04:17",
				"2021-08-16 17:04:17",
				"2021-10-16 17:04:17",
				"2021-12-16 17:04:17",
				"2022-04-16 17:04:17",
			},
		},
		{
			"17 4 17 16 5 MON-SUN/3",
			"2021-02-13 08:02:17",
			[]string{
				"2021-05-16 17:04:17",
				"2022-05-16 17:04:17",
				"2024-05-16 17:04:17",
				"2027-05-16 17:04:17",
				"2030-05-16 17:04:17",
				"2032-05-16 17:04:17",
			},
		},
		{
			"17 4 17 16 5 MON-TUE,FRI-SAT",
			"2021-02-13 08:02:17",
			[]string{
				"2022-05-16 17:04:17",
				"2023-05-16 17:04:17",
				"2025-05-16 17:04:17",
				"2026-05-16 17:04:17",
				"2028-05-16 17:04:17",
				"2031-05-16 17:04:17",
			},
		},
		{
			"17 4 17 16 5 MON-THU/2,FRI-SUN/2",
			"2021-02-13 08:02:17",
			[]string{
				"2021-05-16 17:04:17",
				"2022-05-16 17:04:17",
				"2025-05-16 17:04:17",
				"2027-05-16 17:04:17",
				"2029-05-16 17:04:17",
				"2031-05-16 17:04:17",
			},
		},
		{
			"0 9 18 * * 1 || 0 0 16 * * 5",
			"2024-05-09 17:27:17",
			[]string{
				"2024-05-10 16:00:00",
				"2024-05-13 18:09:00",
				"2024-05-17 16:00:00",
			},
		},
	}

	for _, testCase := range testCases {
		exp, err := ParseCronExpression(testCase.expression)

		if err != nil {
			t.Errorf("could not parse cron expression : %s", err.Error())
			return
		}

		date, err := time.Parse(timeLayout, testCase.time)

		if err != nil {
			t.Errorf("could not parse time : %s", testCase.time)
			return
		}

		for _, nextTimeStr := range testCase.nextTimes {
			nextTime, err := time.Parse(timeLayout, nextTimeStr)

			if err != nil {
				t.Errorf("could not parse next time : %s", nextTimeStr)
				return
			}

			date = exp.NextTime(date)

			if nextTime.Format(timeLayout) != date.Format(timeLayout) {
				t.Errorf("got: %s expected: %s", date, nextTime)
			}
		}
	}

}

func TestParseCronExpression_Errors(t *testing.T) {
	testCases := []struct {
		expression  string
		errorString string
	}{
		{expression: "", errorString: "cron expression must not be empty"},
		{expression: "test * * * * *", errorString: "the value in field SECOND must be number : test"},
		{expression: "5 * * * *", errorString: "cron expression must consist of 6 fields : found 5 in \"5 * * * *\""},
		{expression: "61 * * * * *", errorString: "the value in field SECOND must be between 0 and 59"},
		{expression: "61 * * * * *", errorString: "the value in field SECOND must be between 0 and 59"},
		{expression: "* 65 * * * *", errorString: "the value in field MINUTE must be between 0 and 59"},
		{expression: "* * * 0 * *", errorString: "the value in field DAY_OF_MONTH must be between 1 and 31"},
		{expression: "* * 1-12/0 * * *", errorString: "step must be 1 or higher in \"1-12/0\""},
		{expression: "* * 0-32/5 * * *", errorString: "the value in field HOUR must be between 0 and 23"},
		{expression: "* * * * 0-10/2 *", errorString: "the value in field MONTH must be between 1 and 12"},
		{expression: "* * 1-12/test * * *", errorString: "step must be number : \"test\""},
	}

	for _, testCase := range testCases {
		exp, err := ParseCronExpression(testCase.expression)
		assert.Nil(t, exp, "expression must have been parsed : %s", testCase.expression)
		assert.NotNil(t, err, "an error must have been occurred")
		assert.Equal(t, testCase.errorString, err.Error(),
			"error string must not match, expected : %s, actual :%s", testCase.errorString, err.Error())
	}
}

func TestParseField_WhenValueIsEmpty(t *testing.T) {
	result, err := parseField("", second)
	assert.Nil(t, result, "result must not have been returned")
	assert.NotNil(t, err, "an error must have been occurred")
	assert.Equal(t, "value must not be empty", err.Error())
}
